/**  
* Copyright (c) 2008, Noel Lynch All rights reserved. Redistribution and use  
* in source and binary forms, with or without modification, are permitted  
* provided that the following conditions are met: Redistributions of source  
* code must retain the above copyright notice, this list of conditions and the  
* following disclaimer. Redistributions in binary form must reproduce the above  
* copyright notice, this list of conditions and the following disclaimer in the  
* documentation and/or other materials provided with the distribution. Neither  
* the name of Noel Lynch nor the names of contributors may be used to  
* endorse or promote products derived from this software without specific prior  
* written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT  
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A  
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR  
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,  
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,  
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;  
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,  
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR  
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF  
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.  
*/
// Oct 10, 2008

package com.sceneworker.editors.trimesheditor;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.concurrent.Callable;

import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;

import com.jme.math.FastMath;
import com.jme.math.Matrix3f;
import com.jme.math.Quaternion;
import com.jme.math.Ray;
import com.jme.math.Triangle;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.TriMesh;
import com.jme.util.GameTaskQueueManager;

abstract	public class	OrthoViewPanel extends JComponent implements MouseMotionListener {
	static	final	public	int		kTolerance = 2;
	
	protected		TriMesh		mcl_triMesh;
	
	public		Point		mcl_centre3D = new Point();
	public		Point		mcl_center2D = new Point();
	
	private		Point		mcl_lastPoint = null;
	
	protected		Vector3f		mcl_eyeDir = new Vector3f();
	protected		Vector3f		kEyeLoc = new Vector3f(-1000, -1000, -1000);		// meaningless due to new depth in ortho
	
	protected		float		mf_zoom = 1f;
	
	protected		boolean		mb_renderFrontFaceOnly;
	
	static	protected		TriStruct		mcla_orthoTris[];
	protected				Rectangle		mcl_selectedPtRect;				
	
	static	protected		int				mi_curTriSel = -1;
	static	protected		int				mi_selPt = -1;
	
	protected	Matrix3f		mcl_worldToOrthoProjection;
	protected	Matrix3f		mcl_orthoToWorldProjection;
	
	protected	boolean			mb_viewNormals = false;
	
	public	OrthoViewPanel(TriMesh cl_tm) {
		mcl_triMesh = cl_tm;
		
		initialiseTriStruct();
		
		final	OrthoViewPanel		lcl_this = this;
		
		addMouseMotionListener(this);
		
		addMouseListener(new MouseAdapter() {

			@Override
			public void mouseReleased(MouseEvent cl_e) {
				mcl_lastPoint = null;
				mi_selPt = -1;
				
				if(cl_e.isPopupTrigger()) {
					OrthoMeshViewMenu	lcl_popUp = new OrthoMeshViewMenu(lcl_this);
					lcl_popUp.show(lcl_this, cl_e.getX(), cl_e.getY());
				}
			}

			@Override
			public void mouseClicked(MouseEvent cl_e) {
				requestFocusInWindow();
			}

			@Override
			public void mousePressed(MouseEvent cl_e) {
				super.mousePressed(cl_e);
				
				if(cl_e.isPopupTrigger()) {
					OrthoMeshViewMenu	lcl_popUp = new OrthoMeshViewMenu(lcl_this);
					lcl_popUp.show(lcl_this, cl_e.getX(), cl_e.getY());
				}
				
				// tri selected..try to select a point
				if(mi_curTriSel != -1) {
					selectPt(cl_e);
					
					// no point selected then try to select a new tri
					if(mi_selPt == -1) {
						doTriClick(cl_e);
					}
				} else {
					doTriClick(cl_e);
				}
				
				
			}
		});
		
		addKeyListener(new KeyAdapter() {

			@Override
			public void keyPressed(KeyEvent cl_e) {
				if(cl_e.getKeyChar() == 'a') {
					
				}
				
				if(cl_e.getKeyChar() == 'd') {
					
				}
				
				if(cl_e.getKeyChar() == 'w') {
					
				}
				
				if(cl_e.getKeyChar() == 's') {
					
				}
				
				if(cl_e.getKeyChar() == 'q') {
					
				}
				
				if(cl_e.getKeyChar() == 'z') {
					
				}
				
				repaint();
			}
			
		});
		
		requestFocusInWindow();
	}
	
	abstract	protected	void	renderInfo(Graphics2D cl_g);
	abstract	protected	void	flipView();
	
	// temps
	protected		Point		mcl_a = new Point();
	protected		Point		mcl_b = new Point();
	protected		Point		mcl_c = new Point();
	
	protected	Point		worldToOrtho(Vector3f cl_v, Point cl_pt) {
		Point		lcl_pt;
		
		if(cl_pt == null) {
			lcl_pt = new Point();
		} else {
			lcl_pt = cl_pt;
		}
		
		Vector3f		lcl_newV = mcl_worldToOrthoProjection.mult(cl_v);
		lcl_pt.x = mcl_center2D.x + (int)(lcl_newV.x * mf_zoom);
		lcl_pt.y = mcl_center2D.y + (int)(lcl_newV.y * mf_zoom);
		
		return lcl_pt;
	}
	
	protected	Vector3f		orthoToWorld(Point cl_pt, Vector3f cl_v) {
		Vector3f		lcl_v;
		
		if(cl_v == null) {
			lcl_v = new Vector3f();
		} else {
			lcl_v = cl_v;
		}
		
		lcl_v.x = (float)(cl_pt.x - mcl_center2D.x) / mf_zoom;
		lcl_v.y = (float)(cl_pt.y - mcl_center2D.y) / mf_zoom;
		
		lcl_v = mcl_orthoToWorldProjection.mult(lcl_v);
		
		return lcl_v;
		
	}
	
	@Override
	public Dimension getPreferredSize() {
		return new Dimension(300, 300);
	}

	public	void	setRenderFrontFaceOnly(boolean b_on) {
		mb_renderFrontFaceOnly = b_on;
	}
	
	public	void	setZoom(float f_z) {
		mf_zoom = f_z;
		repaint();
	}
	
	protected	void	renderTri(Graphics2D cl_g, Vector3f cl_a, Vector3f cl_b, Vector3f cl_c) {
		worldToOrtho(cl_a, mcl_a);
		worldToOrtho(cl_b, mcl_b);
		worldToOrtho(cl_c, mcl_c);
		
		cl_g.drawLine(mcl_a.x, mcl_a.y, mcl_b.x, mcl_b.y);
		cl_g.drawLine(mcl_b.x, mcl_b.y, mcl_c.x, mcl_c.y);
		cl_g.drawLine(mcl_c.x, mcl_c.y, mcl_a.x, mcl_a.y);
	}
	
	protected	void	renderPoint(Graphics2D cl_g, Vector3f cl_a) {
		Point	lcl_pt = worldToOrtho(cl_a, null);
		cl_g.fillRect(lcl_pt.x - 2, lcl_pt.y - 2, 4, 4);
	}
	
	protected	void	initialiseTriStruct() {
		FloatBuffer		lcla_vertices = mcl_triMesh.getVertexBuffer();
		IntBuffer		lcla_indices = mcl_triMesh.getIndexBuffer();
		
		mcla_orthoTris = new TriStruct[lcla_indices.capacity() / 3];
		
		Vector3f		lcl_a = new Vector3f();
		Vector3f		lcl_b = new Vector3f();
		Vector3f		lcl_c = new Vector3f();
		
		float			lf_temp;
		int				li_temp;
		float			lf_dot = -1;
		int				li_triCnt = 0;
		
		for(int li_index = 0; li_index < lcla_indices.capacity(); li_index += 3, li_triCnt++) {
			mcla_orthoTris[li_triCnt] = new TriStruct();
			mcla_orthoTris[li_triCnt].mcl_tri = new Triangle(new Vector3f(), new Vector3f(), new Vector3f());
			
			li_temp = lcla_indices.get(li_index) * 3;
			mcla_orthoTris[li_triCnt].mia_indices[0] = li_temp;
			
			lcl_a.x = lcla_vertices.get(li_temp);
			lcl_a.y = lcla_vertices.get(li_temp + 1);
			lcl_a.z = lcla_vertices.get(li_temp + 2);
			
			li_temp = lcla_indices.get(li_index + 1) * 3;
			mcla_orthoTris[li_triCnt].mia_indices[1] = li_temp;
			
			lcl_b.x = lcla_vertices.get(li_temp);
			lcl_b.y = lcla_vertices.get(li_temp + 1);
			lcl_b.z = lcla_vertices.get(li_temp + 2);
			
			li_temp = lcla_indices.get(li_index + 2) * 3;
			mcla_orthoTris[li_triCnt].mia_indices[2] = li_temp;
			
			lcl_c.x = lcla_vertices.get(li_temp);
			lcl_c.y = lcla_vertices.get(li_temp + 1);
			lcl_c.z = lcla_vertices.get(li_temp + 2);
			
			mcla_orthoTris[li_triCnt].mcl_tri.set(0, lcl_a);
			mcla_orthoTris[li_triCnt].mcl_tri.set(1, lcl_b);
			mcla_orthoTris[li_triCnt].mcl_tri.set(2, lcl_c);
			mcla_orthoTris[li_triCnt].mcl_tri.calculateNormal();
			
		}
		
		/*System.out.println("view " + mi_drawMode);
		for(int li_index = 0; li_index < mcla_orthoTris.length; li_index++) {
			System.out.println(mcla_orthoTris[li_index].mcla_verts[0] + " " + mcla_orthoTris[li_index].mcla_verts[1] + " " + mcla_orthoTris[li_index].mcla_verts[2]);
		}*/
	}
	
	@Override
	protected void paintComponent(Graphics cl_g) {
		Graphics2D		lcl_g = (Graphics2D)cl_g.create();
		
		lcl_g.setColor(Color.lightGray);
		lcl_g.fillRect(0, 0, getWidth(), getHeight());
		
		lcl_g.setColor(Color.red);
		lcl_g.drawLine(0, getHeight() >> 1, getWidth(), getHeight() >> 1);
		lcl_g.drawLine(getWidth() >> 1, 0, getWidth() >> 1, getHeight());
		
		mcl_center2D.x = (getWidth() >> 1) + mcl_centre3D.x;
		mcl_center2D.y = (getHeight() >> 1) + mcl_centre3D.y;
		
		float			lf_dot = -1;
		TriStruct		lcl_temp;
		int				li_temp;
		FloatBuffer		lcla_verts = mcl_triMesh.getVertexBuffer();
		FloatBuffer		lcla_normals = mcl_triMesh.getNormalBuffer();
		Vector3f		lcl_v = new Vector3f();
		Vector3f		lcl_n = new Vector3f();
		boolean			lb_normalCapacityOk = false;
		
		if(lcla_normals != null) {
			lb_normalCapacityOk = (lcla_normals.capacity() >= (lcla_verts.capacity())) ? true : false;
		}
		
		for(int li_index = 0; li_index < mcla_orthoTris.length; li_index++) {
			lcl_temp = mcla_orthoTris[li_index];
			
			lf_dot = -1;
			if(mb_renderFrontFaceOnly) {
				lf_dot = lcl_temp.mcl_tri.getNormal().dot(mcl_eyeDir);
			}
			
			if(lf_dot <= 0) {
				lcl_g.setColor(Color.white);
				if(mi_curTriSel != li_index) {
					renderTri(lcl_g, lcl_temp.mcl_tri.get(0), lcl_temp.mcl_tri.get(1), lcl_temp.mcl_tri.get(2));
				}
				
				
				lcl_g.setColor(Color.blue);
				if(mb_viewNormals && lb_normalCapacityOk) {
					for(int li_vIndex = 0; li_vIndex < 3; li_vIndex++) {
						li_temp = lcl_temp.mia_indices[li_vIndex];
						
						lcl_v.x = lcla_verts.get(li_temp);
						lcl_v.y = lcla_verts.get(li_temp + 1);
						lcl_v.z = lcla_verts.get(li_temp + 2);
						
						lcl_n.x = lcla_normals.get(li_temp);
						lcl_n.y = lcla_normals.get(li_temp + 1);
						lcl_n.z = lcla_normals.get(li_temp + 2);
						
						worldToOrtho(lcl_v, mcl_a);
						
						lcl_n.multLocal(10f);
						lcl_n.addLocal(lcl_v);
						
						worldToOrtho(lcl_n, mcl_b);
						
						lcl_g.drawLine(mcl_a.x, mcl_a.y, mcl_b.x, mcl_b.y);
					}
					
					
				}
			}
			
			
		}
		
		if(mi_curTriSel != -1) {
			// draw selected triangle
			lcl_g.setColor(Color.green);
			renderTri(lcl_g, mcla_orthoTris[mi_curTriSel].mcl_tri.get(0), mcla_orthoTris[mi_curTriSel].mcl_tri.get(1), mcla_orthoTris[mi_curTriSel].mcl_tri.get(2));
			
			lcl_g.setColor(Color.blue);
			renderPoint(lcl_g, mcla_orthoTris[mi_curTriSel].mcl_tri.get(0));
			renderPoint(lcl_g, mcla_orthoTris[mi_curTriSel].mcl_tri.get(1));
			renderPoint(lcl_g, mcla_orthoTris[mi_curTriSel].mcl_tri.get(2));
			
			lcl_g.setColor(Color.red);
			/*if(mcl_selectedPtRect != null) {
				lcl_g.fill(mcl_selectedPtRect);
			}*/
			
		}
		
		lcl_g.setColor(Color.BLACK);
		lcl_g.setFont(new Font("Helvetica", Font.PLAIN, 11));
		
		renderInfo(lcl_g);
		
		lcl_g.dispose();
	}
		
	public void mouseDragged(MouseEvent cl_e) {
		if(mcl_lastPoint == null) { 
			mcl_lastPoint = cl_e.getPoint();
			return;
		}
		
		if(mi_selPt != -1) {
			dragPt(cl_e);
			getParent().repaint();
			return;
		} else {
			// drag center
			Point		lcl_pt = cl_e.getPoint();
			
			mcl_centre3D.x -= (mcl_lastPoint.x - lcl_pt.x);
			mcl_centre3D.y -= (mcl_lastPoint.y - lcl_pt.y);
			
			mcl_lastPoint = lcl_pt;
		}
		
		
		repaint();
	}

	protected	void	doTriClick(MouseEvent cl_e) {
		
		Vector3f		lcl_origin = orthoToWorld(cl_e.getPoint(), null);
		Vector3f		lcl_originLoc = kEyeLoc.mult(mcl_eyeDir);
		lcl_origin.addLocal(lcl_originLoc);
		
		Ray		lcl_pickRay = new Ray(lcl_origin, mcl_eyeDir);
		Vector3f		lcl_intersection = new Vector3f();
		Vector3f		lcl_intersectionTemp = new Vector3f();
		float			lf_lowestDistance = Float.MAX_VALUE;
		mi_curTriSel = -1;
		float			lf_dot;
		
		for(int li_index = 0; li_index < mcla_orthoTris.length; li_index++) {
			// is this triangle facing us?
			lf_dot = mcla_orthoTris[li_index].mcl_tri.getNormal().dot(mcl_eyeDir);
			
			if(lf_dot <= 0) {
				// find an intersection if any
				if(lcl_pickRay.intersectWhere(mcla_orthoTris[li_index].mcl_tri, lcl_intersectionTemp)) {
					float		lf_distSq = lcl_origin.distanceSquared(lcl_intersectionTemp);
					
					if(lf_distSq < lf_lowestDistance) {
						lf_lowestDistance = lf_distSq;
						mi_curTriSel = li_index;
					}
				}
			}
		}
	
		/*if(mi_curTriSel != -1) {
			System.out.println("intersection " + mcla_orthoTris[mi_curTriSel].mcl_tri.get(0) + " " + 
					mcla_orthoTris[mi_curTriSel].mcl_tri.get(1) + " " + mcla_orthoTris[mi_curTriSel].mcl_tri.get(2));
		} else {
			System.out.println("no intersection ");
		}*/
		
		this.getParent().repaint();
	}
	
	protected	void	selectPt(MouseEvent cl_e) {
		mi_selPt = -1;
		mcl_selectedPtRect = null;
		
		// attempt to find a point that is selected
		TriStruct		lcl_tri = mcla_orthoTris[mi_curTriSel];
		Rectangle			lcla_screenPts[] = new Rectangle[3];

		lcla_screenPts[0] = new Rectangle();
		worldToOrtho(lcl_tri.mcl_tri.get(0), mcl_a);
		lcla_screenPts[0].x = mcl_a.x - 2;
		lcla_screenPts[0].y = mcl_a.y - 2;
		
		lcla_screenPts[1] = new Rectangle();
		worldToOrtho(lcl_tri.mcl_tri.get(1), mcl_b);
		lcla_screenPts[1].x = mcl_b.x - 2;
		lcla_screenPts[1].y = mcl_b.y - 2;
		
		lcla_screenPts[2] = new Rectangle();
		worldToOrtho(lcl_tri.mcl_tri.get(2), mcl_c);
		lcla_screenPts[2].x = mcl_c.x - 2;
		lcla_screenPts[2].y = mcl_c.y - 2;
		
		lcla_screenPts[0].width = lcla_screenPts[1].width = lcla_screenPts[2].width = lcla_screenPts[0].height = 
			lcla_screenPts[1].height = lcla_screenPts[2].height = 4;

		for(int li_index = 0; li_index < lcla_screenPts.length; li_index++) {
			if(lcla_screenPts[li_index].contains(cl_e.getPoint())) {
				mi_selPt = li_index;
				mcl_selectedPtRect = lcla_screenPts[li_index];
				return;
			}
		}
	}
	
	protected	void	dragPt(MouseEvent cl_e) {
		if(mcl_selectedPtRect != null) {
			mcl_selectedPtRect.x = cl_e.getPoint().x;
			mcl_selectedPtRect.y = cl_e.getPoint().y;
			
			Point			lcl_txPt = new Point(mcl_selectedPtRect.x, mcl_selectedPtRect.y);
			final	Vector3f		lcl_pt = orthoToWorld(lcl_txPt, null);
			
			TriStruct		lcl_tri = mcla_orthoTris[mi_curTriSel];
			
			final	int		li_effectedIndex = lcl_tri.mia_indices[mi_selPt];
			
			GameTaskQueueManager.getManager().update(new Callable<Void>() {

				public Void call() throws Exception {
					FloatBuffer		lfa_verts = mcl_triMesh.getVertexBuffer();
					
					if(mcl_eyeDir.x == 0) {
						lfa_verts.put(li_effectedIndex, lcl_pt.x);
					}
					if(mcl_eyeDir.y == 0) {
						lfa_verts.put(li_effectedIndex + 1, lcl_pt.y);
					}
					if(mcl_eyeDir.z == 0) {
						lfa_verts.put(li_effectedIndex + 2, lcl_pt.z);
					}
					
					updateTriStruct(li_effectedIndex, new Vector3f(lfa_verts.get(li_effectedIndex), 
							lfa_verts.get(li_effectedIndex + 1), lfa_verts.get(li_effectedIndex + 2)));
					
					return null;
				}
				
			});
			
			/*lcl_tri.mcl_tri.get(mi_selPt).x = lcl_pt.x;
			lcl_tri.mcl_tri.get(mi_selPt).y = lcl_pt.y;
			
			System.out.println(lcl_tri.mcl_tri.get(mi_selPt));*/
		}
	}

	public void mouseMoved(MouseEvent arg0) {
	}
	
	class	OrthoMeshViewMenu extends JPopupMenu {
		public	OrthoViewPanel		mcl_viewPanel;
		
		public	OrthoMeshViewMenu(OrthoViewPanel cl_par) {
			mcl_viewPanel = cl_par;
			
			JMenu			lcl_zoomMenu = new JMenu("Zoom");
			add(lcl_zoomMenu);

			buildZoomMenuItem(0.01f, lcl_zoomMenu);
			buildZoomMenuItem(0.05f, lcl_zoomMenu);
			buildZoomMenuItem(0.1f, lcl_zoomMenu);
			buildZoomMenuItem(0.25f, lcl_zoomMenu);
			buildZoomMenuItem(0.5f, lcl_zoomMenu);
			buildZoomMenuItem(1f, lcl_zoomMenu);
			buildZoomMenuItem(2f, lcl_zoomMenu);
			buildZoomMenuItem(4f, lcl_zoomMenu);
			buildZoomMenuItem(10f, lcl_zoomMenu);
			buildZoomMenuItem(20f, lcl_zoomMenu);
			buildZoomMenuItem(50f, lcl_zoomMenu);
			
			final	JCheckBoxMenuItem		lcl_renderFF = new JCheckBoxMenuItem("Render Front Face Only");
			lcl_renderFF.setState(mcl_viewPanel.mb_renderFrontFaceOnly);
			lcl_renderFF.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent arg0) {
					mcl_viewPanel.mb_renderFrontFaceOnly = lcl_renderFF.getState();
					mcl_viewPanel.repaint();
				}
			});
			add(lcl_renderFF);
		
			JMenuItem		lcl_flipMItem = new JMenuItem("Flip View");
			lcl_flipMItem.addActionListener(new ActionListener() {

				public void actionPerformed(ActionEvent arg0) {
					mcl_viewPanel.flipView();
					mcl_viewPanel.repaint();
				}
				
			});
			add(lcl_flipMItem);
			
			JMenuItem		lcl_viewNormals = new JMenuItem("View Normals");
			lcl_viewNormals.addActionListener(new ActionListener() {

				public void actionPerformed(ActionEvent e) {
					mcl_viewPanel.mb_viewNormals = true;
					mcl_viewPanel.repaint();
				}
				
			});
			add(lcl_viewNormals);
		}
		
		private		void	buildZoomMenuItem(final float f_z, JMenu cl_zoom) {
			JMenuItem		lcl_mi = new JMenuItem((int)(f_z * 100f) + "%");
			lcl_mi.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent arg0) {
					mcl_viewPanel.setZoom(f_z);
				}
			});
			cl_zoom.add(lcl_mi);
		}
	}

	
	
	
	protected	void	updateTriStruct(int i_index, Vector3f cl_pt) {
		int		li_in;
		for(TriStruct lcl_tri : mcla_orthoTris) {
			li_in = lcl_tri.vertexIndexInTri(i_index);
			
			if(li_in != -1) {
				lcl_tri.mcl_tri.set(li_in, cl_pt);
			}
			
		}
	}
}
